/*
 * jnlp-loader
 * Copyright (C) 2009  John Pritchard, jdp@syntelos.org
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package loader.sandbox;

import lxl.io.Caller;
import lxl.net.Find;

import loader.Extension;
import loader.Jar;
import loader.Jnlp;
import loader.Nativelib;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.StringTokenizer;

import org.xml.sax.SAXException;

/**
 * Delete unpacked class files found to perform reflection including
 * dynamic name binding (Class.forName), or access to java.io.File,
 * java.lang.Runtime and java.lang.ClassLoader.
 * 
 * 
 * @see http://code.google.com/p/jnlp-loader/issues/detail?id=6
 * @author jdp
 */
public class WebLoader
    extends loader.Main
    implements Runnable
{

    /**
     * Reference to application class loader used by {@link Jnlp}
     */
    static WebLoader Instance;

    public static void main(String[] argv){
        try {
            WebLoader main = new WebLoader(argv);
            main.run();
        }
        catch (Exception exc){
            exc.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Code access policy
     * 
     * @return OK
     */
    public static boolean CodeAccessPolicy(String classname){
        if (null != classname){
            Boolean screen = DisallowedClasses.get(classname);
            if (null != screen)
                return screen.booleanValue();
            else {
                for (int cc = 0; cc < DisallowedPackagesCount; cc++){
                    if (classname.startsWith(DisallowedPackages[cc]))
                        return false;
                }
                return true;
            }
        }
        else
            return false;
    }
    public final static boolean CallerIsIn(){
        if (Caller.IsIn(WebLoader.Instance))
            return true;
        else
            throw new IllegalStateException();
    }
    public final static boolean CallerIsNotIn(){
        if (Caller.IsNotIn(WebLoader.Instance))
            return true;
        else
            throw new IllegalStateException();
    }

    static URL GetCodebase(){
        return WebLoader.Instance.getCodebase();
    }


    /**
     * @param argv Main function arguments.
     */
    public WebLoader(String[] argv)
        throws IOException, SAXException
    {
        super(argv);

        Instance = this;

        ServiceManager.Create();

        UrlHandler.Create();
    }
    /**
     * @param dir Provide a static directory name or default to jnlp href name.
     * @param argv Main function arguments.
     */
    public WebLoader(String dir, String[] argv)
        throws IOException, SAXException
    {
        super(dir,argv);

        Instance = this;

        ServiceManager.Create();

        UrlHandler.Create();
    }

    protected boolean runInitJnlpAccept(Jnlp jnlp, Extension ext){
        return true;
    }
    protected boolean runInitJnlpAccept(Jnlp jnlp, Jar ext){
        return true;
    }
    protected final boolean runInitJnlpAccept(Jnlp jnlp, Nativelib ext){
        return false;
    }
    protected final void runJnlpCheck(){
        Find find = new Find();

        while (find.hasNext()){
            File file = find.next();
            if (file.getName().endsWith(".class")){
                try {
                    ClassFile classfile = new ClassFile(file);

                    if (AllowSuper(classfile)){
                        boolean dropped = false;
                        /*
                         * Scan field references from class
                         */
                        ClassFile.CPInfo[] fieldReferences = classfile.cpFieldRefs();
                        if (null != fieldReferences){
                            for (ClassFile.CPInfo fieldRef : fieldReferences){
                                if (AllowFieldRef(fieldRef,classfile)){

                                    if (WebLoader.Debug){
                                        String bclass = fieldRef.getClass(classfile);
                                        String bname = fieldRef.getName(classfile);
                                        String btype = fieldRef.getType(classfile);

                                        System.err.println(String.format("%s: allowed class '%s' with field-ref cl '%s' na '%s' ty '%s'.",Thread.currentThread().getName(),classfile.getName(),bclass,bname,btype));
                                    }

                                    continue;
                                }
                                else {
                                    dropped = file.delete();

                                    if (WebLoader.Test){
                                        String bclass = fieldRef.getClass(classfile);
                                        String bname = fieldRef.getName(classfile);
                                        String btype = fieldRef.getType(classfile);

                                        System.err.println(String.format("%s: dropped class '%s' with field-ref cl '%s' na '%s' ty '%s'.",Thread.currentThread().getName(),classfile.getName(),bclass,bname,btype));
                                    }
                                }
                            }
                        }
                        if (Debug || (!dropped)){
                            /*
                             * Scan method references from class
                             */
                            ClassFile.CPInfo[] methodReferences = classfile.cpMethodRefs();
                            if (null != methodReferences){
                                for (ClassFile.CPInfo methodRef : methodReferences){
                                    if (AllowMethodRef(methodRef,classfile)){

                                        if (WebLoader.Debug){
                                            String bclass = methodRef.getClass(classfile);
                                            String bname = methodRef.getName(classfile);
                                            String btype = methodRef.getType(classfile);

                                            System.err.println(String.format("%s: allowed class '%s' with method-ref cl '%s' na '%s' ty '%s'.",Thread.currentThread().getName(),classfile.getName(),bclass,bname,btype));
                                        }

                                        continue;
                                    }
                                    else {
                                        dropped = file.delete();
                                        if (WebLoader.Test){
                                            String bclass = methodRef.getClass(classfile);
                                            String bname = methodRef.getName(classfile);
                                            String btype = methodRef.getType(classfile);

                                            System.err.println(String.format("%s: dropped class '%s' with method-ref cl '%s' na '%s' ty '%s'.",Thread.currentThread().getName(),classfile.getName(),bclass,bname,btype));
                                        }
                                    }
                                }
                            }
                            if (Debug || (!dropped)){
                                /*
                                 * Scan fields of class
                                 */
                                ClassFile.FieldInfo[] fields = classfile.cpFields();
                                if (null != fields){
                                    for (ClassFile.FieldInfo field: fields){

                                        if (AllowField(field,classfile)){

                                            if (WebLoader.Debug){
                                                String name = field.getName(classfile);
                                                String signature = CleanBClass(field.getSignature(classfile));


                                                System.err.println(String.format("%s: allowed class '%s' with field name '%s' type '%s'.",Thread.currentThread().getName(),classfile.getName(),name,signature));
                                            }

                                            continue;
                                        }
                                        else {
                                            dropped = file.delete();

                                            if (WebLoader.Test){
                                                String name = field.getName(classfile);
                                                String signature = CleanBClass(field.getSignature(classfile));

                                                System.err.println(String.format("%s: dropped class '%s' for field name '%s' type '%s'.",Thread.currentThread().getName(),classfile.getName(),name,signature));
                                            }
                                        }
                                    }
                                }

                                if (Debug || (!dropped)){
                                    /*
                                     * Scan methods of class
                                     */
                                    ClassFile.MethodInfo[] methods = classfile.cpMethods();
                                    if (null != methods){
                                        for (ClassFile.MethodInfo method: methods){

                                            if (AllowMethod(method,classfile)){

                                                if (WebLoader.Debug){

                                                    String name = method.getName(classfile);
                                                    String signature = CleanMClass(method.getSignature(classfile));

                                                    System.err.println(String.format("%s: allowed class '%s' with method name '%s' type '%s'.",Thread.currentThread().getName(),classfile.getName(),name,signature));
                                                }

                                                continue;
                                            }
                                            else {
                                                dropped = file.delete();
                                                if (WebLoader.Test){

                                                    String name = method.getName(classfile);
                                                    String signature = CleanMClass(method.getSignature(classfile));

                                                    System.err.println(String.format("%s: dropped class '%s' for method name '%s' type '%s'.",Thread.currentThread().getName(),classfile.getName(),name,signature));
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else {
                        file.delete();

                        if (WebLoader.Test){
                            System.err.println(String.format("%s: dropped file '%s' for superclass.",Thread.currentThread().getName(),file.getPath()));
                        }
                    }
                }
                catch (Exception any){

                    file.delete();

                    if (WebLoader.Test){
                        System.err.println(String.format("%s: dropped file '%s' for error '%s'.",Thread.currentThread().getName(),file.getPath(),any.toString()));

                        if (WebLoader.Debug)
                            any.printStackTrace();
                    }
                }
            }
        }
    }

    /*
     * access policy actors
     */
    protected static boolean AllowSuper(ClassFile classfile){
        String superclass = classfile.getNameSuper();

        return CodeAccessPolicy(superclass);
    }
    protected static boolean AllowFieldRef(ClassFile.CPInfo fieldRef, ClassFile classfile){

        String bclass = fieldRef.getClass(classfile);

        return CodeAccessPolicy(bclass);
    }
    protected static boolean AllowMethodRef(ClassFile.CPInfo methodRef, ClassFile classfile){

        String bclass = methodRef.getClass(classfile);

        return CodeAccessPolicy(bclass);
    }

    protected static boolean AllowField(ClassFile.FieldInfo field, ClassFile classfile){

        String signature = field.getSignature(classfile);

        signature = CleanBClass(signature);

        if (null != signature)
            return CodeAccessPolicy(signature);
        else
            return true;
    }
    protected static boolean AllowMethod(ClassFile.MethodInfo method, ClassFile classfile){

        String signature = method.getSignature(classfile);

        signature = CleanMClass(signature);

        if (null != signature){
            StringTokenizer strtok = new StringTokenizer(signature,",");
            while (strtok.hasMoreTokens()){
                if (!CodeAccessPolicy(strtok.nextToken())){
                    return false;
                }
            }
        }
        return true;
    }

    /*
     * string trimmers
     */
    protected static String CleanBClass(String bc){
        if (null != bc && 0 != bc.length()){
            while ('[' == bc.charAt(0))
                bc = bc.substring(1);

            return (bc.substring(1,bc.length()-1));
        }
        else
            return null;
    }
    protected static String CleanMClass(String bc){
        if (null != bc && 0 != bc.length()){
            while ('(' == bc.charAt(0))
                bc = bc.substring(1);
            int idx = bc.lastIndexOf(')');
            return CleanBClass(bc.substring(0,idx));
        }
        else
            return null;
    }

    /*
     * access policy data
     */
    private final static String[] DisallowedPackages = {
        "com.sun.java.",
        "com.sun.net.",
        "sun.net."
    };
    private final static int DisallowedPackagesCount = DisallowedPackages.length;

    private final static java.util.Map<String,Boolean> DisallowedClasses = new java.util.HashMap<String,Boolean>();
    static {
        DisallowedClasses.put("java.lang.Class",Boolean.FALSE);
        DisallowedClasses.put("java.lang.ClassLoader",Boolean.FALSE);
        DisallowedClasses.put("java.lang.reflect.Field",Boolean.FALSE);
        DisallowedClasses.put("java.lang.reflect.Method",Boolean.FALSE);
        DisallowedClasses.put("java.net.URLClassLoader",Boolean.FALSE);
        DisallowedClasses.put("java.security.SecureClassLoader",Boolean.FALSE);
        DisallowedClasses.put("java.lang.Runtime",Boolean.FALSE);
        DisallowedClasses.put("java.lang.System",Boolean.FALSE);
        DisallowedClasses.put("java.io.File",Boolean.FALSE);
        DisallowedClasses.put("java.io.RandomAccessFile",Boolean.FALSE);
        DisallowedClasses.put("java.io.FileInputStream",Boolean.FALSE);
        DisallowedClasses.put("java.io.FileOutputStream",Boolean.FALSE);
        DisallowedClasses.put("java.net.DatagramSocket",Boolean.FALSE);
        DisallowedClasses.put("java.net.DatagramSocketImpl",Boolean.FALSE);
        DisallowedClasses.put("java.net.DatagramSocketImplFactory",Boolean.FALSE);
        DisallowedClasses.put("java.net.MulticastSocket",Boolean.FALSE);
        DisallowedClasses.put("java.net.PlainDatagramSocketImpl",Boolean.FALSE);
        DisallowedClasses.put("java.net.PlainDatagramSocketImplFactory",Boolean.FALSE);
        DisallowedClasses.put("java.net.ServerSocket",Boolean.FALSE);
        DisallowedClasses.put("java.net.Socket",Boolean.FALSE);
        DisallowedClasses.put("java.net.SocketImpl",Boolean.FALSE);
        DisallowedClasses.put("java.net.SocketFactory",Boolean.FALSE);
        DisallowedClasses.put("java.net.URLStreamHandler",Boolean.FALSE);
        DisallowedClasses.put("java.net.URLStreamHandlerFactory",Boolean.FALSE);
        DisallowedClasses.put("loader.Main",Boolean.FALSE);
        DisallowedClasses.put("loader.Loader",Boolean.FALSE);
        DisallowedClasses.put("loader.Jar",Boolean.FALSE);
        DisallowedClasses.put("loader.Nativelib",Boolean.FALSE);
        DisallowedClasses.put("loader.Extension",Boolean.FALSE);
        DisallowedClasses.put("loader.Clean",Boolean.FALSE);
        DisallowedClasses.put("loader.Find",Boolean.FALSE);
    }

}
